package info.batcloud.fanli.core.service.impl;

import com.ctospace.archit.common.pagination.Paging;
import info.batcloud.fanli.core.dto.AgentDTO;
import info.batcloud.fanli.core.dto.CityAgentDTO;
import info.batcloud.fanli.core.dto.DistrictAgentDTO;
import info.batcloud.fanli.core.constants.CacheNameConstants;
import info.batcloud.fanli.core.entity.Agent;
import info.batcloud.fanli.core.entity.CityAgent;
import info.batcloud.fanli.core.entity.DistrictAgent;
import info.batcloud.fanli.core.entity.User;
import info.batcloud.fanli.core.enums.AgentStatus;
import info.batcloud.fanli.core.enums.AgentType;
import info.batcloud.fanli.core.helper.PagingHelper;
import info.batcloud.fanli.core.repository.*;
import info.batcloud.fanli.core.service.AgentService;
import info.batcloud.fanli.core.service.RegionService;
import info.batcloud.fanli.core.service.UserService;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.inject.Inject;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Predicate;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

import static info.batcloud.fanli.core.enums.AgentStatus.DELETED;

@Service
@CacheConfig(cacheNames = CacheNameConstants.AGENT)
public class AgentServiceImpl implements AgentService {

    @Inject
    private AgentRepository agentRepository;

    @Inject
    private RelationAgentRepository relationAgentRepository;

    @Inject
    private CityAgentRepository cityAgentRepository;

    @Inject
    private DistrictAgentRepository districtAgentRepository;

    @Inject
    private UserRepository userRepository;

    @Inject
    private UserService userService;

    @Inject
    private RegionService regionService;

    @Override
    public Paging<AgentDTO> search(SearchParam param) {
        Specification<Agent> specification = (root, query, cb) -> {
            Predicate predicate = cb.conjunction();
            List<Expression<Boolean>> expressions = predicate.getExpressions();
            if (param.getPerpetual() != null) {
                expressions.add(cb.equal(root.get("perpetual"), param.getPerpetual()));
            }

            if (StringUtils.isNotBlank(param.getPhone())) {
                expressions.add(cb.like(root.get("user").get("phone"), param.getPhone()));
            }
            if (param.getStartUpdateTime() != null) {
                expressions.add(cb.greaterThanOrEqualTo(root.get("updateTime"), param.getStartUpdateTime()));
            }
            if (param.getEndUpdateTime() != null) {
                expressions.add(cb.greaterThanOrEqualTo(root.get("updateTime"), param.getEndUpdateTime()));
            }
            if (param.getType() != null) {
                expressions.add(cb.equal(root.get("type"), param.getEndUpdateTime()));
            }
            if (param.getStatus() != null) {
                expressions.add(cb.greaterThanOrEqualTo(root.get("status"), param.getStatus()));
            } else {
                expressions.add(cb.notEqual(root.get("status"), AgentStatus.DELETED));
            }
            return predicate;
        };
        Sort sort = new Sort(Sort.Direction.DESC, "updateTime");
        Pageable pageable = new PageRequest(param.getPage() - 1,
                param.getPageSize(), sort);
        Page<Agent> page = agentRepository.findAll(specification, pageable);
        return PagingHelper.of(page, item -> toAgent(item), param.getPage(), param.getPageSize());
    }

    @Override
    @Transactional
    @CacheEvict(key = "'IS_AGENT_' + #param.getUserId()")
    public AgentDTO addAgent(AgentAddParam param) {
        User user = userRepository.findOne(param.getUserId());
        //如果当前user不是顶级用户，那么需要修改其为顶级用户
        if (user.getSuperUser() != null) {
            //这里需要修改user的层级关系,并且同步修改其所有下线的层级关系
            userService.changeSuperUser(param.getUserId(), null);
        }
        Date now = new Date();
        Date startTime;
        Agent agent = null;
        if (agent == null) {
//            agent = new Agent();
            agent.setCreateTime(now);
            startTime = now;
        } else {
            if (agent.getStatus() != AgentStatus.VALID) {
                agent.setCreateTime(now);
                startTime = now;
            } else {
                startTime = agent.getExpireTime();
            }

        }
        agent.setUpdateTime(new Date());
        if (param.getYear() == -1) {
            //如果是-1那么说明是永久代理
            agent.setExpireTime(DateUtils.addYears(startTime, 100));
            agent.setPerpetual(true);
        } else {
            agent.setExpireTime(DateUtils.addYears(startTime, param.getYear()));
        }
        agent.setStatus(AgentStatus.VALID);
        agent.setUser(user);
        agentRepository.save(agent);
        return toAgent(agent);
    }

    @Override
    public List<AgentDTO> findAgentByUserId(long userId) {
        List<Agent> list = agentRepository.findByUserIdAndStatusIsNot(userId, DELETED);
        return list.stream().map(a -> toAgent(a)).collect(Collectors.toList());
    }

    @Override
    @Cacheable(key = "'IS_' + #userId + '_AGENT_' + #agentType.name()")
    public boolean isAgent(long userId, AgentType agentType) {
        Agent agent = null;
        switch (agentType) {
            case CITY:
                agent = cityAgentRepository.findByUserIdAndStatusIsNot(userId, DELETED);
                break;
            case DISTRICT:
                agent = districtAgentRepository.findByUserIdAndStatusIsNot(userId, DELETED);
                break;
            case RELATION:
                agent = relationAgentRepository.findByUserIdAndStatusIsNot(userId, DELETED);
                break;
        }
        if (agent == null) {
            return false;
        }
        if (agent.getStatus() != AgentStatus.VALID) {
            return false;
        }
        if (agent.isPerpetual()) {
            return agent.getAgentType() == agentType;
        }
        if (agent.getExpireTime().before(new Date())) {
            return false;
        }
        return agent.getAgentType() == agentType;
    }

    @Override
    public CityAgentDTO findCityAgentByCityId(long cityId) {
        CityAgentDTO agentBO = new CityAgentDTO();
        return toAgent(cityAgentRepository.findByCityIdAndStatusIsNot(cityId, DELETED), agentBO);
    }

    @Override
    public DistrictAgentDTO findDistrictAgentByDistrictId(long districtId) {
        DistrictAgentDTO agentBO = new DistrictAgentDTO();
        return toAgent(districtAgentRepository.findByDistrictIdAndStatusIsNot(districtId, DELETED), agentBO);
    }

    @Override
    public boolean isValid(AgentDTO agent) {
        if (agent.getStatus() != AgentStatus.VALID) {
            return false;
        }
        if (agent.getExpireTime().before(new Date())) {
            return false;
        }
        return true;
    }

    @Override
    @CacheEvict(key = "'IS_AGENT_' + #id")
    public void setStatus(long id, AgentStatus status) {
        Agent entity = agentRepository.findOne(id);
        entity.setStatus(status);
        agentRepository.save(entity);
    }

    private String getAgentRegion(Agent agent) {
        switch (agent.getAgentType()) {
            case CITY:
                return regionService.findById(((CityAgent) agent).getCityId()).getName();
            case DISTRICT:
                String city = regionService.findById(((DistrictAgent) agent).getCityId()).getName();
                String district = regionService.findById(((DistrictAgent) agent).getDistrictId()).getName();
                return city + district;
            case RELATION:
                return AgentType.RELATION.getTitle();
            default:
                return "";
        }
    }

    private AgentDTO toAgent(Agent entity) {
        if(entity == null) {
            return null;
        }
        AgentDTO agentDTO = new AgentDTO();
        BeanUtils.copyProperties(entity, agentDTO);
        AgentDTO.User user = new AgentDTO.User();
        BeanUtils.copyProperties(entity.getUser(), user);
        agentDTO.setUser(user);
        agentDTO.setType(entity.getAgentType());
        agentDTO.setRegion(getAgentRegion(entity));
        return agentDTO;
    }

    private <T extends AgentDTO> T toAgent(Agent agent, T agentBO) {
        if(agent == null) {
            return null;
        }
        BeanUtils.copyProperties(agent, agentBO);
        AgentDTO.User user = new AgentDTO.User();
        BeanUtils.copyProperties(agent.getUser(), user);
        agentBO.setUser(user);
        agentBO.setType(agent.getAgentType());
        agentBO.setRegion(getAgentRegion(agent));
        return agentBO;
    }
}
